<template>
  <div class="flex-column-layout">
    <!-- 搜索框 -->
    <ElInput
      v-if="filter.filterBy !== undefined"
      v-model="filterWord"
      v-bind="{
        size: filter.size || 'default',
        placeholder: filter.placeholder || '请输入关键字',
      }"
      @input="filterTable"
      style="margin-bottom: 6px"
    />
    <!-- 表格 -->
    <component :is="tableVNode" />
    <!-- 分页器 -->
    <ElPagination
      v-if="isPaging"
      style="margin: 0 12px 6px auto"
      v-model:current-page="currentPage"
      :hide-on-single-page="pageCount === 0"
      :total="dataTotal"
      v-bind="paginationProps"
    />
  </div>
</template>

<script lang="ts" setup generic="T extends Record<string, any>">
import { h, ref, computed, getCurrentInstance } from "vue";
import { ElTable, ElTableColumn, ElPagination, ElInput } from "element-plus";
import type { PaginationProps } from "element-plus";
import type { FilterConfig, TableColumn, TableProps } from "./types";
import type { ComponentPublicInstance } from "vue";
import { debounce } from "lodash";

const props = defineProps({
  tableColumns: {
    type: Array as () => TableColumn<T>[],
    default: () => [],
  },
  tableProps: {
    type: Object as () => TableProps<T>,
    default: () => ({}),
  },
  paginationProps: {
    type: Object as () => Partial<
      Omit<PaginationProps, "total" | "CurrentPage" | "hideOnSinglePage">
    > & {
      /** 每页显示多少条数据 0 关闭分页 */
      pageSize?: number;
    },
    default: () => ({}),
  },
  filter: {
    type: Object as () => FilterConfig<T>,
    default: () => ({}),
  },
});

// 分页默认属性
const paginationProps = {
  pageSize: 20,
  background: true,
  layout: "total, prev, pager, next, jumper",
  ...props.paginationProps,
};

/** 判断是否分页 */
const isPaging = computed(() => {
  return paginationProps.pageSize > 0;
});

// 创建过滤防抖函数
const debouncedFilter = debounce(
  (prop: string, data: any[], callback: (result: any[]) => void) => {
    if (typeof props.filter?.filterBy === "function") {
      callback(props.filter?.filterBy(prop, data));
    } else if (typeof props.filter?.filterBy === "string") {
      const key = props.filter.filterBy;
      callback(data.filter((item) => item[key].includes(prop)));
    }
  },
  300
);

// 搜索内容
const filterWord = ref("");
// 过滤后数据
const filteredData = ref<T[] | null>(null);
// 过滤事件
const filterTable = (value: string) => {
  const { data = [] } = props.tableProps;
  // 执行防抖过滤函数
  debouncedFilter(value, data, (res) => {
    if (filterWord.value !== "") {
      filteredData.value = [...res];
    } else {
      filteredData.value = null;
    }
  });
};

// 获取当前实例
const vm = getCurrentInstance();
const tableRef = (Instance: Element | ComponentPublicInstance | null) => {
  if (!vm) return;
  vm.exposed = vm.exposeProxy = Instance || {};
};

/** 数据总数 */
const dataTotal = computed(() => {
  const { data = [] } = props.tableProps;
  return data.length || 0;
});
/** 页码数 */
const pageCount = computed(() => {
  if (dataTotal.value === 0 || paginationProps.pageSize === 0) return 0;
  return Math.ceil(dataTotal.value / paginationProps.pageSize);
});
/** 当前页码 */
const currentPage = ref<number>(1);

/** 显示到列表的数据 */
const tableData = computed(() => {
  const { data = [] } = props.tableProps;
  // 计算最终显示的数据
  const displayData = filteredData.value || data;
  // 如果不分页，则直接返回所有数据
  if (!isPaging.value) return displayData;
  // 获取当前页的数据
  const start = (currentPage.value - 1) * paginationProps.pageSize;
  const end = start + paginationProps.pageSize;
  // 获取当前页的数据
  return displayData.slice(start, end);
});

// 创建表格组件
const tableVNode = () => {
  const { data = [], style = {}, ...tableProps } = props.tableProps;
  return h(
    ElTable,
    {
      ref: tableRef,
      data: tableData.value,
      style: { ...style, "margin-bottom": pageCount.value > 0 ? "6px" : "0" },
      ...tableProps,
    },
    () =>
      props.tableColumns.map((column) => {
        const { Default, header, filterIcon, expand, ...columnProps } = column;
        return h(
          ElTableColumn,
          { ...columnProps, key: column.prop },
          { default: Default, header, "filter-icon": filterIcon, expand }
        );
      })
  );
};
</script>

<style lang="less" scoped>
.flex-column-layout {
  display: flex;
  flex-direction: column;
  flex: 1 0 0;
  overflow: hidden;
}
</style>
